#!/usr/bin/env python
#
# Copyright 2016, Data61, CSIRO (ABN 41 687 119 230)
#
# SPDX-License-Identifier: BSD-2-Clause

import sys, tempita, re, argparse
from lxml import etree

# ------------------------------------------- Configuration ----------------------------------------
DESCRIPTION = """\
CIDL - Simple C IDL Compiler is made to save the trouble of manually implementing
RPC interface stubs to marshal / unmarshal C style arguments and return value.
CIDL uses a simple XML-based IDL language.
"""
TITLE_MESSAGE = """\
/* DO NOT EDIT MANUALLY!!!
   This file was generated by CIDL.

   Copyright 2016, Data61, CSIRO (ABN 41 687 119 230)

   SPDX-License-Identifier: BSD-2-Clause
*/
"""
IMPL_WEAK_ATTRIB = '__attribute__((weak))'
HEADER_PFX = 'refos-rpc/'
CPTR_TYPE = 'seL4_CPtr'
CSLOT_TYPE = 'seL4_CSlot'
TEMPLATE_ROOT = open('cidl_templates/root.py', 'r').read()
TEMPLATE_CLIENT_HEADER = open('cidl_templates/client_header.py', 'r').read()
TEMPLATE_CLIENT = open('cidl_templates/client.py', 'r').read()
TEMPLATE_SERVER_HEADER = open('cidl_templates/server_header.py', 'r').read()
TEMPLATE_SERVER = open('cidl_templates/server.py', 'r').read()
TEMPLATE_DISPATCHER = open('cidl_templates/dispatcher.py', 'r').read()
TEMPLATE_DISPATCHER_CONTAINER = open('cidl_templates/dispatcher_container.py', 'r').read()
TEMPLATE_ENUM = open('cidl_templates/enum.py', 'r').read()

# ------------------------------------------ Helper Functions --------------------------------------

def get_if_name(if_name):
    if_name = re.sub(r'.xml$', '', if_name.strip())
    if_name = re.search('([^\/]*)$', if_name).group(0)
    if_name = re.sub(r'_interface$', '', if_name)
    return if_name

def get_headerfile_name(if_name, client_mode):
    return (HEADER_PFX + get_if_name(if_name) + '_%s.h')%('client' if client_mode else 'server')

def preprocess(filestr, pre_process = True):
    if not pre_process:
        return filestr
    filestr_ = ""
    for fileline in filestr.splitlines():
        if fileline.startswith("#") == False:
            filestr_ += fileline
    return filestr_.replace("\n",'').replace(r'    ','').replace(r'____', '    ').\
           replace("\\n", "\n").replace("\\\\", "\\")

# ---------------------------------------- Arguments Processing ------------------------------------

CONNECT_EP = ''
def process_arg(name_idl, type_idl, dr = '', mode_idl = None, lenvar_idl = ''):
    dir_idl = 'in';
    if dr == 'out':
        dir_idl = 'out'

    apfx = ''
    aref = ''
    apsfx = ''

    if type_idl in ['int', 'int32_t', 'uint32_t', 'char', 'uintptr_t',
                    'size_t', 'void*', 'cslot', CSLOT_TYPE]:
        type_internal = 'uint'
    elif type_idl in ['cptr', CPTR_TYPE]:
        type_internal = 'cptr'
    elif type_idl == 'char*':
        type_internal = 'str'
    elif type_idl.endswith('*'):
        type_internal = 'buf'
        if mode_idl == 'array': apfx = '_array'; apsfx = ', ' + lenvar_idl
    else:
        type_internal = 'buf'
        aref = '&'

    if mode_idl is None: mode_idl = 'normal'
    if mode_idl == 'length': dir_idl = 'length'
    if mode_idl == 'connect_ep':
        global CONNECT_EP;
        CONNECT_EP = name_idl
        dir_idl = 'neither'

    return (dir_idl, (type_idl, type_internal, name_idl, mode_idl, dir_idl, apfx, aref, apsfx))

def process_arg_idl(arg_idl):
    return process_arg(arg_idl.get("name"), arg_idl.get("type"), arg_idl.get("dir"),\
                       arg_idl.get("mode"), arg_idl.get("lenvar"))

def process_arg_last(alist, return_type = '', ralist = []):
    if return_type != '' and return_type != 'void':
        (_, arg_obj) = process_arg('__ret__', return_type, 'out')
        alist.append(arg_obj)
        ralist.append(arg_obj)
    if len(alist) <= 0:
        return
# ---------------------------------------- Generator functions -------------------------------------
def process_function(func_idl, template_str, dct_func = {}, dbg = True):
    global CONNECT_EP

    alist = []
    oalist = []
    calist = []
    ralist = []

    for arg_idl in func_idl:
        (dr, arg_obj) = process_arg_idl(arg_idl)
        if arg_obj is None:
            continue
        calist.append(arg_obj)
        if dr == 'in':
            alist.append(arg_obj)
        elif dr == 'out':
            oalist.append(arg_obj)

    process_arg_last(alist)
    process_arg_last(oalist, func_idl.get('return'), ralist)
    process_arg_last(calist)

    dct_func['alist'] = alist
    dct_func['oalist'] = oalist
    dct_func['calist'] = calist
    dct_func['ralist'] = ralist
    dct_func['fname'] = func_idl.get('name')
    dct_func['return_type'] = func_idl.get('return')
    dct_func['comment_text'] = re.sub(r'^ +', '   ', func_idl.text.strip(), flags = re.MULTILINE)
    dct_func['connect_ep'] = str(CONNECT_EP)
    dct_func['weak_attrib'] = IMPL_WEAK_ATTRIB if dbg else ''

    return tempita.Template(template_str).substitute(dct_func)

def process(filename, client, header, disp, pre_process, dbg):
    xml_idl = etree.parse(filename).getroot()

    dct_root = {}
    dct_root['includes'] = []
    dct_root['includes'].append('#include <%srpc.h>' % HEADER_PFX)
    dct_root['ifname'] = get_if_name(filename)
    dct_root['label_min'] = xml_idl.get('label_min');
    dct_root['default_connect_ep'] = xml_idl.get('connect_ep')
    dct_root['header_mode'] = header
    dct_root['client_mode'] = client
    if not header:
        dct_root['includes'].append('#include <%s>' % get_headerfile_name(filename, client))

    if disp:
        template_str = TEMPLATE_DISPATCHER
    elif header:
        template_str = TEMPLATE_CLIENT_HEADER if client else TEMPLATE_SERVER_HEADER
    else:
        template_str = TEMPLATE_CLIENT if client else TEMPLATE_SERVER

    template_str = preprocess(template_str, pre_process)
    template_enum = preprocess(TEMPLATE_ENUM, pre_process)
    template_root = preprocess(TEMPLATE_ROOT, pre_process)
    if disp:
        template_root = preprocess(TEMPLATE_DISPATCHER_CONTAINER, pre_process)

    dct_root['func_list'] = []; dct_root['enum_list'] = []
    for x in xml_idl:
        if x.tag == 'include':
            dct_root['includes'].append(re.sub(r'^\s*', '#include <', x.text.strip(),\
                                               flags = re.MULTILINE) + '>\n')
            continue
        dct_root['func_list'].append(process_function(x, template_str, dct_root, dbg))
        dct_root['enum_list'].append(process_function(x, template_enum, dct_root, dbg))
    print(TITLE_MESSAGE)
    print(tempita.Template(template_root).substitute(dct_root))

parser = argparse.ArgumentParser(description = DESCRIPTION)
parser.add_argument('-r', '--header', action='store_true',\
    help='generate header declarations.')
parser.add_argument('-c', '--client', action='store_true',\
    help='generate header/src files for the RPC client.')
parser.add_argument('-s', '--server', action='store_true',\
    help='generate header/src files for the RPC server.')
parser.add_argument('-d', '--dispatcher', action='store_true',\
    help='generate dispatcher source file for RPC server.')
parser.add_argument('-n', '--no_preprocess', action='store_false',\
    help='skip pre-process template files.')
parser.add_argument('-g', '--debug', action='store_true',\
    help='debug mode, adds weak symbols to handler implementation functions.')
parser.add_argument('-v', '--version', action='version',\
    version='%(prog)s 1.2 Wed 14 Aug 2013 13:57:15 EST ')
parser.add_argument('filename', metavar='IDL_FILE', action='store', \
    help='the XML-based IDL file to compile.')
args = parser.parse_args()

process(args.filename, args.client, args.header, args.dispatcher, args.no_preprocess, args.debug)
